from coin2.service.order_detector.window_rate_tracker import (IpRateTrackManager, get_host_ips)
from coin2.service.order_detector.influxdb import InfluxdbOutPut
from coin2.service.order_detector.common import (
    QuerySpec, OrderContext, OpenOrderElement, DETECTOR_INTERVAL, get_exchange_rate_totol, StratDetectorContext, OneSecToNs)
import coin2.service.order_detector.store as store
from concurrent.futures import ThreadPoolExecutor
from coin2.service.order_detector.detector_interface import (
    get_query_num_resq, detector_open_order)
from threading import Lock

import json
import time
import copy


class DetectorServer():
  def __init__(self, exchange: str) -> None:
    self._exchange = exchange
    self._influxdb = store.InfluxdbOrderStorage()
    self._store_raw_info = store.TopicRecordStorage(self._exchange)
    self._store_abnormal_open_order = store.AbnormalOpenOrderStorage(self._exchange)
    self._store_abnormal_close_order = store.AbnormalCloseOrderStorage(self._exchange)
    self._ip_manager = IpRateTrackManager(self._exchange)
    self._strategy_name_set = set()
    self._dc_map = {}
    self._detectro_interval = DETECTOR_INTERVAL
    self._exit = False
    self._pthread_pool = None
    self._output_lock = Lock()
    self._update_config_list = []
    self._update = False
    self._first_boot = True

    ips = get_host_ips()
    totol = get_exchange_rate_totol(self._exchange)
    for ip in ips:
      self._ip_manager.add_ip(ip, OneSecToNs * 60, totol, totol)

    self._pthread_pool = ThreadPoolExecutor(max_workers=len(ips) + 1)

  def update_config(self, detector_config_list : list):
    self._update_config_list = detector_config_list
    self._update = True

  def __add_last_open_order(self):
    strategy_ele_map = store.AbnormalOpenOrderStorage.load_abnormal_open_order(self._exchange)
    for _, dc in self._dc_map.items():
      if dc._query_spec.strategy_name in strategy_ele_map:
        dc.update_by_element(strategy_ele_map, time.time_ns())


  def __update_detector_config(self):
    new_strategy_set = set()
    for _, dc in self._dc_map.items():
      while dc._submitted:
        time.sleep(0.1)

    for info in self._update_config_list:
      spec = QuerySpec(strategy_name=info["strategy_name"], exchange=info["exchange"], market_type=info["market_type"],
                       api_version=info["api_version"], owner=info["owner"], key_path=info["key_path"])
      if "native_symbols" in info:
        spec.set_native_symbols(info["native_symbols"])

      if spec.strategy_name not in self._dc_map:
        dc = StratDetectorContext(spec)
        self._dc_map[dc._query_spec.strategy_name] = dc
      else:
        self._dc_map[spec.strategy_name].set_query_spec(spec)

      new_strategy_set.add(spec.strategy_name)

    del_set = self._strategy_name_set - new_strategy_set
    for strategy_name in del_set:
      del(self._dc_map[strategy_name])

    if self._first_boot:
      self.__add_last_open_order()
      self._first_boot = False

    self._strategy_name_set = new_strategy_set
    self._update = False
    self._update_config_list = []

    print("Detector config update,  exchange ", self._exchange,
          "detecotr list size ", len(self._dc_map))
  
  def __detector_order(self, dc: StratDetectorContext):
    num_resq = get_query_num_resq(dc.query_spec)
    ip = ""
    while len(ip) == 0:
      ip = self._ip_manager.get_ip(num_resq)
      if len(ip) == 0:
        time.sleep(0.1)
      else:
        break

    spec = copy.deepcopy(dc.query_spec)
    spec.set_ip(ip)

    try:
      ele_list, raw = detector_open_order(spec)
    except Exception as e:
      print(spec.spce(), "detector open order exception ", e)
      dc._submitted = False
      return

    timestamp = time.time_ns()
    raw_dic = {"spce": dc.query_spec.spce(), "info": raw}
    self._store_raw_info.write(raw_dic)
    dc.update_by_element(ele_list, timestamp)
    abnormal_close_list = dc.abnormal_close_order_list
    abnormal_open_list = dc.abnormal_open_order_list

    self._output_lock.acquire()
    self._influxdb.write_by_ele_list(dc.query_spec, ele_list, timestamp)
    self._store_abnormal_open_order.write(dc.query_spec, abnormal_open_list)

    if len(abnormal_close_list) > 0:
      self._store_abnormal_close_order.write(dc.query_spec, abnormal_close_list)
    if len(abnormal_open_list) > 0:
      self._influxdb.write_by_abnormal_open_oc(dc.query_spec, abnormal_open_list, timestamp)

    self._output_lock.release()
    dc._submitted = False


  def run(self):
    while self._exit != True:
      if self._update:
        self.__update_detector_config()
      for _, dc in self._dc_map.items():
        assert (isinstance(dc, StratDetectorContext))
        timestamp = time.time_ns()
        if timestamp - dc._last_query_time > self._detectro_interval and dc._submitted == False:
          self._pthread_pool.submit(self.__detector_order, dc)
          dc._submitted = True
        if self._exit:
          break
        time.sleep(0.1)

      if self._exit:
        break

    self._pthread_pool.shutdown()
    self._store_raw_info.close()
    self._store_abnormal_close_order.close()
    self._store_abnormal_open_order.close()
    self._influxdb.close()
    print("DetecotorServer exit exchange ", self._exchange)

  def exit(self):
    self.exit = True

# StratDetectorContext()
